package demo.db.nacos;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.EventListener;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ListView;
import com.alibaba.nacos.api.naming.selector.NamingSelector;
import com.alibaba.nacos.client.naming.listener.AbstractNamingChangeListener;
import com.alibaba.nacos.client.naming.listener.NamingChangeEvent;
import com.alibaba.nacos.client.naming.selector.NamingSelectorFactory;
import demo.java.lang.ThreadDemo;
import demo.java.util.concurrent.ThreadFactoryDemo;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Nacos注册中心
 */
public class NacosNamingDemo {

    private static Logger logger = LoggerFactory.getLogger(NacosConfigDemo.class);
    String serverAddr = "192.168.41.35:8848";

    /**
     * 由于同一个Nacos Client实例，仅能向一个服务注册一个实例；若同一个Nacos Client实例多次向同一个服务注册实例，后注册的实例将会覆盖先注册的实例。
     *
     * @throws NacosException
     */
    @Test
    public void registerInstance() throws NacosException {

        String serviceName = "nacos.test.service";
        String groupName = "DEFAULT_GROUP";
        String ip = "127.0.0.1";
        int port = 8848;
        String clusterName = "DEFAULT";

        // 初始化配置中心的Nacos Java SDK
        NamingService naming = NacosFactory.createNamingService(serverAddr);

        EventListener serviceListener = new AbstractNamingChangeListener() {


            @Override
            public void onChange(NamingChangeEvent event) {
                System.err.println("-----------------监听服务下的实例列表变化-----------------");
                if (event.isAdded()) {
                    logger.info("新增：{}", event.getAddedInstances());
                }
                if (event.isRemoved()) {
                    logger.info("删除：{}", event.getRemovedInstances());
                }
                if (event.isModified()) {
                    logger.info("修改：{}", event.getModifiedInstances());
                }
                List<Instance> instanceList = event.getInstances();
                logger.info("变更后实例列表：{}", instanceList);
            }

            @Override
            public Executor getExecutor() {
                return Executors.newSingleThreadExecutor(new ThreadFactoryDemo.NamedThreadFactory("监听者"));
            }
        };
        System.err.println("""
                自定义的数据选择器:
                Nacos Client 提供了预设的多种数据选择器以供默认场景下使用：
                
                Cluster选择器，NamingSelectorFactory.newClusterSelector(Collection<String> clusters), 当订阅服务时传入了clusters参数，Nacos Client将自动使用该数据选择器。
                
                Ip选择器，NamingSelectorFactory.newClusterSelector(String ipRegex)，当实例的ip满足传入的ipRegex时，才会被通知回调。
                
                元数据选择器，NamingSelectorFactory.newMetadataSelector(Map<String, String> metadata)，当实例的元数据包含所有传入选择器的metadata时，才会被通知回调。
                
                任意元数据选择器，NamingSelectorFactory.newMetadataSelector(Map<String, String> metadata, false),当实例的元数据包含任意一对传入选择器的metadata时，才会被通知回调。
                """);
        //         只会选择订阅ip为`127.0`开头的实例。
        NamingSelector selector = NamingSelectorFactory.newIpSelector("127.0.*");

        naming.subscribe(serviceName, selector, serviceListener);

        System.err.println("""
                注册一个实例到服务:
                由于同一个Nacos Client实例，仅能向一个服务注册一个实例；
                若同一个Nacos Client实例多次向同一个服务注册实例，后注册的实例将会覆盖先注册的实例。
                """);
        naming.registerInstance(serviceName, groupName, "127.0.0.0", port, clusterName);
        naming.registerInstance(serviceName, groupName, ip, port, clusterName);
        ThreadDemo.safeSleep(2000);
        System.err.println("获取全部实例");
        List<Instance> instanceList = naming.getAllInstances(serviceName);
        System.err.println(instanceList);

        System.err.println("分页获取服务列表");
        ListView<String> result = naming.getServicesOfServer(1, 10);
        System.err.println(JSON.toJSONString(result, true));

        System.err.println("获取当前客户端所监听的服务列表");
        System.out.println(naming.getSubscribeServices());


        System.err.println("根据负载均衡算法随机获取一个健康实例");
        Instance instance = naming.selectOneHealthyInstance(serviceName);
        System.err.println(instance);


        System.err.println("healthy: 是否健康，为true时仅会返回健康的实例列表，反之则返回不健康的实例列表。");
        System.err.println(naming.selectInstances("nacos.test.service", true));

        System.err.println("subscribe: 是否订阅服务，为true时将会订阅该服务，同时查询优先通过内存缓存；为false时将直接查询Nacos Server");
        System.err.println(naming.selectInstances("nacos.test.service", true, true));

        System.err.println("删除服务下的一个实例。");
        naming.deregisterInstance(serviceName, ip, port, clusterName);

        Instance instance1 = new Instance();
        instance1.setIp("127.0.0.1");
        instance1.setPort(8848);
        instance1.setClusterName("DEFAULT");

        Instance instance2 = new Instance();
        instance2.setIp("127.0.0.1");
        instance2.setPort(9848);
        instance2.setClusterName("DEFAULT");

        List<Instance> instances = new ArrayList<>(2);
        instances.add(instance1);
        instances.add(instance2);
        System.err.println("""
                由于同一个Nacos Client实例，仅能向一个服务注册一个实例；
                若同一个Nacos Client实例多次向同一个服务注册实例，后注册的实例将会覆盖先注册的实例。 
                考虑到社区存在代理注册的场景：如Nacos-Sync， Proxy-Registry等，需要在一个客户端中注册同一个服务的不同实例，
                社区新增了批量注册服务实例的功能。
                """);
        naming.batchRegisterInstance(serviceName, groupName, instances);

        ThreadDemo.safeSleep(2000);

        System.err.println("批量注销服务实例");
        naming.batchDeregisterInstance(serviceName, groupName, instances);

        System.err.println("取消监听服务");
        naming.unsubscribe(serviceName, selector, serviceListener);

        ThreadDemo.safeSleep(2000);

        System.err.println("""
                Nacos的Java SDK 生命周期从创建时开始，到调用shutdown()方法时结束，
                期间对应创建的线程池、连接等均会始终保留，即使连接断开，也会不断重试重新建立连接。
                
                因此在使用时需要注意应用中创建的Nacos Java SDK的实例个数，避免造成线程池和连接的泄漏，
                在更换Nacos Java SDK实例时，切记调用shutdown()方法。
                同时在应用中应尽量复用同一个Nacos Java SDK实例，避免频繁的初始化实例。
                """);
        naming.shutDown();
    }

}
